%% The MIT License
%% 
%% Paralog is licensed under the MIT License.
%% 
%% Copyright (c) 2008 Robert Ahrens <mindangels@yahoo.com>
%% 
%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
%% in the Software without restriction, including without limitation the rights
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%% 
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%% 
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%% 

%% @author 
-module(ets_kb_server).
-behaviour(gen_server).


-export([
         init/1,
         handle_call/3,
         handle_cast/2,
         handle_info/2,
         terminate/2,
         code_change/3,
         start_new/0
        ]).


-compile(export_all).

-include_lib("stdlib/include/qlc.hrl").
-include("pl_syntax.hrl").
-record(state, {tname, tid}).

%% Storage strategy:  Store rules in a set (ie. 1:1) where
%%   * keys are {RuleName, Arity}
%%   * vals are [{Head, Body}]
%%
%% Thus, for a given query, can just look up the one list
%% of {Head, Body} tuples that we need.  When we load and
%% assert, we merely add data to this list.
%%


%%% Public API

start_new() ->
    case gen_server:start(?MODULE, [], [])  of
        {ok, Pid} ->
            ets_kb:new(Pid);
        Error ->
            throw({error, Error})
    end.


%%% gen_server callbacks
init([]) ->
    TName = list_to_atom("pl_kb_" ++ erlang:ref_to_list(erlang:make_ref())),
    TId = ets:new(TName, [set, protected]), 
    {ok, #state{tname=TName, tid=TId}}.


handle_call({lookup, RuleName, Arity}, _From, State=#state{tid=TId})
  when is_atom(RuleName) andalso is_integer(Arity) ->
    {reply, lookup_rule(TId, RuleName, Arity), State}.


handle_cast({load, Rules}, S=#state{tid=Tid}) ->
    load(Tid, Rules),
    {noreply, S};
handle_cast({asserta, Rule}, S=#state{tid=TId}) ->
    asserta(TId, Rule),
    {noreply, S};
handle_cast({assertz, Rule}, S=#state{tid=TId}) ->
    assertz(TId, Rule),
    {noreply, S};
handle_cast(stop, State) ->
    {stop, normal, State}.


handle_info(_, State) ->
    {noreply, State}.


terminate(normal, #state{tname=TName, tid=TId}) ->
    io:format("terminate called.  Clearing ~p [~p]~n", [TName, TId]),
    ok.


code_change(_, State, _) ->
    {ok, State}.


%%% internal functions
lookup_rule(TId, RuleName, Arity) 
  when is_atom(RuleName) andalso is_integer(Arity)->
    case ets:lookup(TId, {RuleName, Arity}) of
        [] -> 
            [];
        [{_, Matches}] ->
            [pls:rule(RuleName, Head, Body) || 
                {Head, Body} <- Matches]
    end.


parse_rule(?rule(Name, Head, Body)) ->
    {Name, Head, Body}.


format_rule({Name, Head, Body}) ->
    {{Name, length(Head)}, {Head, Body}}.


update(TId, Key, Fun) ->
    NewVal = Fun(ets:lookup(TId, Key)),
    ets:insert(TId, {Key, NewVal}).


load(TId, RuleList) ->
    RulesToLoad =
        lists:foldl(
          fun (R, D) ->
                  {K, V} = format_rule(parse_rule(R)),
                  dict:append(K, V, D)
          end,
          dict:new(),
          RuleList
         ),
    dict:fold(
      fun (K, V, _) ->
              update(TId, K, fun (RL) -> RL ++ V end)
      end,
      ok,
      RulesToLoad
     ).


asserta(TId, Rule) ->
    {K, V} = format_rule(parse_rule(Rule)),
    update(TId, K, fun (RL) -> [V|RL] end).


assertz(TId, Rule) ->
    {K, V} = format_rule(parse_rule(Rule)),
    update(TId, K, fun (RL) -> RL ++ [V] end).
                            
                            
                                      
        
